package com.tc.data.spring.validator;

import org.springframework.aop.support.StaticMethodMatcherPointcut;
import org.springframework.transaction.annotation.Transactional;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 *  @author zhixin02.liu
 */
public class TransactionAnnotationPointcut extends StaticMethodMatcherPointcut {
	private List<TransactionAnnotationValidator> validators = new ArrayList<TransactionAnnotationValidator>();
	/**
	 * Cache of white target class, keyed by target Class.
	 */
	final Set<String> whiteList = new HashSet<String>();

	/**
	 *  Only use to validate @Transactional.
	 *  Always return false.
	 */
	@Override
	public boolean matches(Method method, Class<?> targetClass) {
		// validate @Transactional on target class.
		if (!whiteList.contains(targetClass.getCanonicalName())) {
			validateTransactionAnnotation(getClassTransactionAnnotation(targetClass), targetClass.getCanonicalName());
			whiteList.add(targetClass.getCanonicalName());
		}

		// validate @Transactional on method.
		validateTransactionAnnotation(getMethodTransactionAnnotation(method), getMethodName(method, targetClass));

		return false;
	}

	private void validateTransactionAnnotation(Transactional transactional, String name) {
		if (transactional == null)
			return;

		for (TransactionAnnotationValidator validator : validators) {
			validator.validateTransactionAnnotation(transactional, name);
		}
	}

	private Transactional getClassTransactionAnnotation(Class<?> targetClass) {
		return getAnnotations(targetClass, Transactional.class);
	}

	private Transactional getMethodTransactionAnnotation(Method method) {
		return getAnnotations(method, Transactional.class);
	}

	private <T extends Annotation> T getAnnotations(AnnotatedElement ae, Class<T> annotationType) {

		// Scan Raw-annotation
		T ann = ae.getAnnotation(annotationType);
		if (ann != null) {
			return ann;
		}

		// Scan Meta-annotations
		for (Annotation metaAnn : ae.getAnnotations()) {
			ann = metaAnn.annotationType().getAnnotation(annotationType);
			if (ann != null) {
				return ann;
			}
		}

		return null;
	}

	// method and targetClass always not null.
	// AopUtils has validated.
	private String getMethodName(Method method, Class<?> targetClass) {
		return targetClass.getName() + "." + method.getName();
	}

	public void addValidators(List<TransactionAnnotationValidator> validators) {
		this.validators.addAll(validators);
	}
}
